package it.uniroma2.art.owlart.utilities.transform;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.base.Predicate;
import com.google.common.base.Predicates;

import it.uniroma2.art.owlart.exceptions.ModelAccessException;
import it.uniroma2.art.owlart.exceptions.ModelUpdateException;
import it.uniroma2.art.owlart.model.ARTNode;
import it.uniroma2.art.owlart.model.ARTStatement;
import it.uniroma2.art.owlart.model.ARTURIResource;
import it.uniroma2.art.owlart.model.NodeFilters;
import it.uniroma2.art.owlart.models.RDFModel;
import it.uniroma2.art.owlart.navigation.ARTStatementIterator;

public class RDFTransform {
	public static interface Operation {
		boolean execute(RDFModel sourceModel, RDFModel targetModel, ARTStatement stmt) throws Exception;
	};

	public static class StatementPredicate implements Predicate<ARTStatement> {
		private static final Predicate<ARTNode> TRUE = Predicates.alwaysTrue();
		
		private Predicate<ARTNode> subjPredicate;
		private Predicate<ARTNode> predPredicate;
		private Predicate<ARTNode> objPredicate;
		
		public StatementPredicate(Predicate<ARTNode> subjPredicate, Predicate<ARTNode> predPredicate,
				Predicate<ARTNode> objPredicate) {
			this.subjPredicate = subjPredicate;
			this.predPredicate = predPredicate;
			this.objPredicate = objPredicate;
		}

		public StatementPredicate() {
			this(TRUE, TRUE, TRUE);
		}
		
		public StatementPredicate withSubject(Predicate<ARTNode> predicate) {
			this.subjPredicate = predicate;
			return this;
		}
		
		public StatementPredicate withPredicate(Predicate<ARTNode> predicate) {
			this.predPredicate = predicate;
			return this;
		}
		
		public StatementPredicate withObject(Predicate<ARTNode> predicate) {
			this.objPredicate = predicate;
			return this;
		}
	
		public boolean apply(ARTStatement stmt) {
			return subjPredicate.apply(stmt.getSubject()) && predPredicate.apply(stmt.getPredicate())
					&& objPredicate.apply(stmt.getObject());
		}
	}

	public static class BNodePredicate implements Predicate<ARTNode> {
		public boolean apply(ARTNode node) {
			return node.isBlank();
		}
	}
	
	public static class URIPredicate implements Predicate<ARTNode> {
		private ARTURIResource uri;
		
		public URIPredicate withURI(ARTURIResource uri) {
			this.uri = uri;
			return this;
		}

		public boolean apply(ARTNode node) {
			boolean t = node.isURIResource();
			
			if (t) {
				if (uri != null) {
					t = node.asURIResource().equals(uri);
				}
			}
			
			return t;
		}
	}
	
	public static class LiteralPredicate implements Predicate<ARTNode> {
		private Pattern lexicalForm;
		private Pattern lang;
		
		public LiteralPredicate withLexicalForm(String lexicalForm) {
			this.lexicalForm = Pattern.compile("^" + Pattern.quote(lexicalForm) + "$");
			return this;
		}

		public LiteralPredicate withLang(String lang) {
			this.lang = Pattern.compile("^" + Pattern.quote(lang) + "$");
			return this;
		}

		public LiteralPredicate withLexicalForm(Pattern lexicalForm) {
			this.lexicalForm = lexicalForm;
			return this;
		}

		public LiteralPredicate withLang(Pattern lang) {
			this.lang = lang;
			return this;
		}

		
		public boolean apply(ARTNode node) {
			boolean t = node.isLiteral();
			
			if (t) {
				if (lexicalForm != null) {
					t = node.asLiteral().getLabel().equals(lexicalForm);
				}
				
				if (t) {
					if (lang != null) {
						Matcher m = lang.matcher(node.asLiteral().getLanguage());
						t = m.matches();
					}
				}
			}
			
			return t;
		}
	}
	
	public static abstract class AbstractOperation implements Operation {

		private static final Predicate<ARTStatement> TRUE = Predicates.alwaysTrue();
		
		private Predicate<ARTStatement> predicate;

		protected AbstractOperation(Predicate<ARTStatement> predicate) {
			this.predicate = predicate;
		}
		
		public AbstractOperation() {
			this(TRUE);
		}

		public boolean execute(RDFModel sourceModel, RDFModel targetModel, ARTStatement stmt) {
			if (predicate.apply(stmt)) {
				return doTransform(targetModel, targetModel, stmt);
			} else {
				return false;
			}
		}

		protected abstract boolean doTransform(RDFModel sourceModel, RDFModel targetModel, ARTStatement stmt);
	}

	public static class DiscardOperation extends AbstractOperation {
		public DiscardOperation(Predicate<ARTStatement> predicate) {
			super(predicate);
		}
		
		@Override
		protected boolean doTransform(RDFModel sourceModel, RDFModel targetModel, ARTStatement stmt) {
			return true;
		}
	};

	public static Operation RETAIN = new Operation() {

		public boolean execute(RDFModel sourceModel, RDFModel targetModel, ARTStatement stmt)
				throws ModelUpdateException, ModelAccessException {
			if (sourceModel.hasStatement(stmt, false, NodeFilters.MAINGRAPH)) {
				targetModel.addStatement(stmt, NodeFilters.MAINGRAPH);
			}
			return true;
		}
	};

	public void doTransformation(RDFModel sourceModel, RDFModel targetModel) throws Exception {
		ARTStatementIterator it = sourceModel.listStatements(NodeFilters.ANY, NodeFilters.ANY,
				NodeFilters.ANY, true, NodeFilters.MAINGRAPH);

		try {
			while (it.hasNext()) {
				ARTStatement stmt = it.getNext();
				transformStatement(sourceModel, targetModel, stmt);
			}
		} finally {
			it.close();
		}
	}

	private List<Operation> operations = new ArrayList<RDFTransform.Operation>();

	public void appendOperation(Operation operation) {
		operations.add(operation);
	}

	public boolean removeOperation(Operation operation) {
		return operations.remove(operation);
	}

	private void transformStatement(RDFModel sourceModel, RDFModel targetModel, ARTStatement stmt)
			throws Exception {
		for (Operation op : operations) {
			boolean finished = op.execute(sourceModel, targetModel, stmt);
			if (finished == true) {
				break;
			}
		}
	}
}
